package cn.iocoder.yudao.module.hs.service.rfid;

import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.hs.controller.admin.material.vo.MaterialSaveReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.rfid.vo.RfidPageReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.rfid.vo.RfidSaveReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.rfidchangerecord.vo.RfidChangeRecordSaveReqVO;
import cn.iocoder.yudao.module.hs.controller.admin.tray.vo.TraySaveReqVO;
import cn.iocoder.yudao.module.hs.dal.dataobject.material.MaterialDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.rfid.RfidDO;
import cn.iocoder.yudao.module.hs.dal.dataobject.tray.TrayDO;
import cn.iocoder.yudao.module.hs.dal.mysql.rfid.RfidMapper;
import cn.iocoder.yudao.module.hs.service.material.MaterialService;
import cn.iocoder.yudao.module.hs.service.rfidchangerecord.RfidChangeRecordService;
import cn.iocoder.yudao.module.hs.service.tray.TrayService;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.hs.enums.ErrorCodeConstants.*;

/**
 * RFID信息 Service 实现类
 *
 * @author 航盛
 */
@Service
@Validated
public class RfidServiceImpl implements RfidService {

    @Resource
    private RfidMapper rfidMapper;
    @Resource
    private MaterialService materialService;
    @Resource
    private TrayService trayService;
    @Resource
    private RfidChangeRecordService rfidChangeRecordService;

    @Override
    public Long createRfid(RfidSaveReqVO createReqVO) {
        // 插入
        RfidDO rfid = BeanUtils.toBean(createReqVO, RfidDO.class);
        rfidMapper.insert(rfid);
        // 返回
        return rfid.getId();
    }

    @Override
    public void updateRfid(RfidSaveReqVO updateReqVO) {
        // 校验存在
        validateRfidExists(updateReqVO.getId());
        // 更新
        RfidDO updateObj = BeanUtils.toBean(updateReqVO, RfidDO.class);
        rfidMapper.updateById(updateObj);
    }

    @Override
    public void deleteRfid(Long id) {
        // 校验存在
        validateRfidExists(id);
        // 删除
        rfidMapper.deleteById(id);
    }

    private void validateRfidExists(Long id) {
        if (rfidMapper.selectById(id) == null) {
            throw exception(RFID_NOT_EXISTS);
        }
    }

    @Override
    public RfidDO getRfid(Long id) {
        return rfidMapper.selectById(id);
    }

    @Override
    public PageResult<RfidDO> getRfidPage(RfidPageReqVO pageReqVO) {
        return rfidMapper.selectPage(pageReqVO);
    }

    @Override
    public RfidDO getRfid(String rfidCode) {
        return rfidMapper.selectOne(RfidDO::getCode, rfidCode);
    }

    @Override
    public void updateRfidChanged(RfidDO rfidDO) {
        rfidMapper.update(new LambdaUpdateWrapper<RfidDO>()
                .set(RfidDO::getTrayCode, null)
                .set(RfidDO::getWarehousingTime, null)
                .eq(RfidDO::getId, rfidDO.getId()));
    }

    @Override
    @Transactional
    public void changeRfid(String rfidCode, List<String> materialCodeList) {
        RfidDO newRfid = this.getRfid(rfidCode);
        if (newRfid == null) {
            RfidSaveReqVO rfidSaveReqVO = new RfidSaveReqVO();
            rfidSaveReqVO.setCode(rfidCode);
            rfidSaveReqVO.setTrayCode(rfidCode);
            rfidSaveReqVO.setWarehousingTime(LocalDateTime.now());
            rfidSaveReqVO.setLowBattery(false);
            this.createRfid(rfidSaveReqVO);
            newRfid = this.getRfid(rfidCode);
        }
        for (String materialCode : materialCodeList) {
            MaterialDO material = materialService.getMaterial(materialCode);
            if (material == null) {
                throw exception(MATERIAL_NOT_EXISTS);
            }
            TrayDO tray = trayService.getTray(material.getTrayCode());
            if (tray == null) {
                throw exception(TRAY_NOT_EXISTS);
            }
            if (tray.getRfidCode().equals(newRfid.getCode())) {
                continue;
            }
            material.setTrayCode(newRfid.getTrayCode());
            materialService.updateMaterial(BeanUtils.toBean(material, MaterialSaveReqVO.class));
        }
    }

    @Override
    @Transactional
    public void changeRfid(String oldRfidCode, String newRfidCode) {
        RfidDO oldRfid = this.getRfid(oldRfidCode);
        // 校验旧RFID是否存在
        if (oldRfid == null) {
            throw exception(RFID_NOT_EXISTS);
        }
        // 校验旧RFID是否绑定托盘
        if (StrUtil.isEmpty(oldRfid.getTrayCode())) {
            throw exception(RFID_CHANGE_RECORD_OLD_NOT_BIND);
        }
        TrayDO tray = trayService.getTray(oldRfid.getTrayCode());
        if (tray == null) {
            throw exception(TRAY_NOT_EXISTS);
        }
        // 绑定新RFID
        RfidDO newRfid = this.getRfid(newRfidCode);
        if (newRfid != null && StrUtil.isNotEmpty(newRfid.getTrayCode())) {
            throw exception(RFID_CHANGE_RECORD_NEW_BINDED);
        } else {
            RfidSaveReqVO rfidSaveReqVO = new RfidSaveReqVO();
            rfidSaveReqVO.setCode(newRfidCode);
            rfidSaveReqVO.setTrayCode(oldRfid.getTrayCode());
            rfidSaveReqVO.setWarehousingTime(LocalDateTime.now());
            rfidSaveReqVO.setLowBattery(false);
            this.createRfid(rfidSaveReqVO);
        }
        // 更新托盘绑定的RFID
        tray.setRfidCode(newRfidCode);
        TraySaveReqVO updateReqVO = BeanUtils.toBean(tray, TraySaveReqVO.class);
        trayService.updateTray(updateReqVO);

        // 更新旧RFID
        this.updateRfidChanged(oldRfid);

        RfidChangeRecordSaveReqVO createReqVO = new RfidChangeRecordSaveReqVO();
        createReqVO.setOldRfidCode(oldRfidCode);
        createReqVO.setNewRfidCode(newRfidCode);
        rfidChangeRecordService.createRfidChangeRecord(createReqVO);
    }

    @Override
    public RfidDO getRfidByTrayCode(String trayCode) {
        return rfidMapper.selectOne(RfidDO::getTrayCode, trayCode);
    }

    @Override
    public Map<String, RfidDO> getRfidByTrayCode(Set<String> trayCodes) {
        return rfidMapper.selectList(RfidDO::getTrayCode, trayCodes).stream()
                .collect(Collectors.toMap(RfidDO::getTrayCode, rfid -> rfid));
    }

    @Override
    public void bind(String trayCode, String rfidCode) {
        TrayDO tray = trayService.getTray(trayCode);
        if (tray == null) {
            throw exception(TRAY_NOT_EXISTS);
        }
        RfidDO rfid = this.getRfid(rfidCode);
        if (rfid == null) {
            throw exception(RFID_NOT_EXISTS);
        }
        tray.setRfidCode(rfidCode);
        trayService.updateTray(BeanUtils.toBean(tray, TraySaveReqVO.class));
        rfid.setTrayCode(trayCode);
        rfidMapper.updateById(rfid);
    }

    @Override
    public void unbind(Set<String> trayCodes) {
        trayService.updateTrayUnbind(trayCodes);
        LambdaQueryWrapperX<RfidDO> queryWrapper = new LambdaQueryWrapperX<RfidDO>()
                .in(RfidDO::getTrayCode, trayCodes);
        List<RfidDO> rfidDOS = rfidMapper.selectList(queryWrapper);
        Set<String> rfidCodes = rfidDOS.stream().map(RfidDO::getCode).collect(Collectors.toSet());
        LambdaUpdateWrapper<RfidDO> wrapper = new LambdaUpdateWrapper<RfidDO>()
                .in(RfidDO::getCode, rfidCodes)
                .set(RfidDO::getTrayCode, null)
                .set(RfidDO::getWarehousingTime, null);
        rfidMapper.update(wrapper);
    }

    @Override
    public Integer getLowBatteryCount() {
        List<RfidDO> rfidDOS = rfidMapper.selectList(RfidDO::getLowBattery, 1);
        return rfidDOS.size();
    }

    @Override
    public void updateRfidOut(Set<String> rfidCodes, Long toWarehouseDeptId) {
        LambdaUpdateWrapper<RfidDO> updateWrapper = new LambdaUpdateWrapper<RfidDO>()
                .set(toWarehouseDeptId != null, RfidDO::getDeptId, toWarehouseDeptId)
                .in(RfidDO::getCode, rfidCodes);
        rfidMapper.update(updateWrapper);
    }
}